#include <chrono>
#include <iostream>
#include <atomic>
#include <string>
#include <stdio.h>

#include <gtest/gtest.h>
#include <libgo/libgo.h>
#include "../gtest_unit/gtest_exit.h"

#if !defined(TEST_MIN_THREAD)
#define TEST_MIN_THREAD 1
#define TEST_MAX_THREAD 1
#endif

namespace golang {
using namespace std::chrono;

static const int N = 10000000;

template <typename T>
void dump(const std::string& name, int n, T start, T end) {
    long nanos = (long)duration_cast<nanoseconds>(end - start).count();
    long nPerSecond = 100000 * (long)n / nanos;
    printf("%s    %d    %ld ns/op    %ld w/s\n", name.c_str(), n, nanos / n, nPerSecond);
}

void test_atomic() {
    auto start = steady_clock::now();
    std::atomic_long val{ 0 };
    for (int i = 0; i < N; ++i) {
        val += i;
    }
    auto end = steady_clock::now();
    dump("std::atomic.add", N, start, end);
}

void test_switch(int coro) {
    auto start = steady_clock::now();
    std::atomic<int>* done = new std::atomic<int> { 0 };
    for (int i = 0; i < coro; ++i)
        go co_stack(8192) [ = ] {
        for (int i = 0; i < N * 10 / coro; ++i) {
            co_yield;
        }
        if (++*done == coro) {
            auto end = steady_clock::now();
            dump("BenchmarkSwitch_" + std::to_string(coro), N * 10, start, end);
            delete done;
        }
    };
}

void test_channel(int capa, int n) {
    char buf[1024] = {};
    co_chan<bool> ch(capa);
    std::atomic_int c{ 0 };
    auto start = steady_clock::now();
    const int loop = std::max<int>(TEST_MIN_THREAD / 2, 1);
    for (int i = 0; i < loop; ++i) {
        c += 2;
        go[&] {
            for (int i = 0; i < n; ++i) {
                ch << true;
            }
            --c;
        };

        go[&] {
            for (int i = 0; i < n; ++i) {
                ch >> nullptr;
            }
            --c;
        };
    }

    while (c) {
        usleep(1000);
    }
    auto end = steady_clock::now();
    dump("BenchmarkChannel_" + std::to_string(capa), n * loop, start, end);
}

void test_mutex(int n) {
    //typedef co::LFLock mutex_t;
    //typedef std::mutex mutex_t;
    typedef co_mutex mutex_t;
    mutex_t mtx;
    std::atomic_int c{ 0 };
    long val = 0;
    auto start = steady_clock::now();
    for (int i = 0; i < TEST_MIN_THREAD; ++i) {
        ++c;
        go[&] {
            for (int i = 0; i < n; ++i) {
                std::unique_lock<mutex_t> lock(mtx);
                free(malloc(400));
                ++val;
            }
            --c;
        };
    }

    while (c) {
        usleep(1000);
    }
    auto end = steady_clock::now();
    dump("BenchmarkMutex_" + std::to_string(val), val, start, end);
    if (val != n * TEST_MIN_THREAD) {
        printf("ERROR, val=%ld\n", val);
    }
}

};

TEST(golang, test) {
    using namespace golang;

    test_atomic();

    go[] { test_switch(1); };
    WaitUntilNoTask();

    go[] { test_switch(1000); };
    WaitUntilNoTask();

    //go []{ test_mutex(1000000); };
    //WaitUntilNoTask();

    go[] { test_channel(0, 1000000); };
    WaitUntilNoTask();

    go[] { test_channel(1, 1000000); };
    WaitUntilNoTask();

    //go []{ test_channel(63, 1000000); };
    //WaitUntilNoTask();
    //
    //go []{ test_channel(64, 1000000); };
    //WaitUntilNoTask();

    //go []{ test_channel(9999, 10000000); };
    //WaitUntilNoTask();

    //go []{ test_channel(5, 10); };
    go[] { test_channel(10000, 10000000); };

    WaitUntilNoTask();
}
